package main

import (
	"fmt"
	"sync"
	"time"
)

// Queue 用结构定义一个队列，里面封装了一个字符数组，一个 Cond 对象
type Queue struct {
	queue []string
	cond *sync.Cond
}

func main() {

	// 初始化队列
	q := Queue{

		queue: []string{},

		// 构造 Cond 需要传入一个 Locker
		cond: sync.NewCond(&sync.Mutex{}),
	}

	go func() {

		// 起一个协程，在死循环中每隔 2s 向队列中添加数据
		for {
			q.Enqueue("a")
			time.Sleep(time.Second*2)
		}
	}()

	for {

		// 再起一个协程每 1s 从队列中读数据
		q.Dequeue()
		time.Sleep(time.Second)
	}
}

// Enqueue 为结构 Queue 添加入队方法
func (q *Queue)Enqueue(item string)  {

	q.cond.L.Lock()
	defer q.cond.L.Unlock()

	// 向 q 的字符串数组中 append 数据
	q.queue = append(q.queue, item)
	fmt.Printf("putting %s to queue, notify all\n", item)

	// 向 Queue 中的 Cond 发送广播消息，相当于 Java 中的 notifyAll()
	q.cond.Broadcast()
}

// Dequeue 为结构 Queue 添加出队方法
func (q *Queue) Dequeue() string {

	q.cond.L.Lock()
	defer q.cond.L.Unlock()

	// 如果 q 中的字符串数组中没有字符串，就调用 q.cond 将自己挂起，相当于 Java 中的 wait()
	if len(q.queue) == 0 {
		fmt.Println("no data available, wait")

		// 如果生产线程调用了 q.cond.Broadcast()，由 q.cond.Wait() 导致阻塞的线程将恢复
		q.cond.Wait()
	}

	// 如果 q 中的字符串中有数据，取出第 0 个数据并将其删除
	result := q.queue[0]
	q.queue = q.queue[1:]

	return result
}
